<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
    />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Medieval Fantasy Book (with Phaser)</title>
    <link rel="stylesheet" href="/css/examples.css?ver=1.0.0" />
    <script src="/js/examples.js?ver=1.1.1"></script>
    <script src="/lib/phaser.min.js?ver=3.52.0"></script>
    <script src="/lib/enable3d/enable3d.phaserExtension.0.22.0.min.js"></script>
  </head>

  <body>
    <div id="info-text">Use WASD, SPACE and your Mouse.<br />Try to play it on your mobile device :)</div>
    <script>
      const {
        enable3d,
        Scene3D,
        Canvas,
        ThirdDimension,
        THREE,
        JoyStick,
        ExtendedObject3D,
        ThirdPersonControls,
        PointerLock,
        PointerDrag
      } = ENABLE3D

      /**
       * Is touch device?
       */
      const isTouchDevice = 'ontouchstart' in window

      class MainScene extends Scene3D {
        constructor() {
          super({ key: 'MainScene' })
        }

        init() {
          this.accessThirdDimension({ maxSubSteps: 10, fixedTimeStep: 1 / 120 })
          this.third.renderer.outputEncoding = THREE.LinearEncoding
          this.canJump = true
          this.move = false

          this.moveTop = 0
          this.moveRight = 0
        }

        async create() {
          const { lights } = await this.third.warpSpeed('-ground', '-orbitControls')

          const { hemisphereLight, ambientLight, directionalLight } = lights
          const intensity = 0.65
          hemisphereLight.intensity = intensity
          ambientLight.intensity = intensity
          directionalLight.intensity = intensity

          this.third.physics.add.box({ y: 10, x: 35 }, { lambert: { color: 'red' } })

          // this.third.physics.debug.enable()

          /**
           * Medieval Fantasy Book by Pixel (https://sketchfab.com/stefan.lengyel1)
           * https://sketchfab.com/3d-models/medieval-fantasy-book-06d5a80a04fc4c5ab552759e9a97d91a
           * Attribution 4.0 International (CC BY 4.0)
           */
          this.third.load.gltf('/assets/glb/book.glb').then(object => {
            const scene = object.scenes[0]

            const book = new ExtendedObject3D()
            book.name = 'scene'
            book.add(scene)
            this.third.add.existing(book)

            // add animations
            // sadly only the flags animations works
            object.animations.forEach((anim, i) => {
              book.mixer = this.third.animationMixers.create(book)
              // overwrite the action to be an array of actions
              book.action = []
              book.action[i] = book.mixer.clipAction(anim)
              book.action[i].play()
            })

            book.traverse(child => {
              if (child.isMesh) {
                child.castShadow = child.receiveShadow = false
                child.material.metalness = 0
                child.material.roughness = 1

                if (/mesh/i.test(child.name)) {
                  this.third.physics.add.existing(child, {
                    shape: 'concave',
                    mass: 0,
                    collisionFlags: 1,
                    autoCenter: false
                  })
                  child.body.setAngularFactor(0, 0, 0)
                  child.body.setLinearFactor(0, 0, 0)
                }
              }
            })
          })

          /**
           * box_man.glb by Jan Bláha
           * https://github.com/swift502/Sketchbook
           * CC-0 license 2018
           */
          this.third.load.gltf('/assets/glb/box_man.glb').then(object => {
            const man = object.scene.children[0]

            this.man = new ExtendedObject3D()
            this.man.name = 'man'
            this.man.rotateY(Math.PI + 0.1) // a hack
            this.man.add(man)
            this.man.rotation.set(0, Math.PI * 1.5, 0)
            this.man.position.set(35, -3.5, 0)
            // add shadow
            this.man.traverse(child => {
              if (child.isMesh) {
                child.castShadow = child.receiveShadow = true
                // https://discourse.threejs.org/t/cant-export-material-from-blender-gltf/12258
                child.material.roughness = 1
                child.material.metalness = 0
              }
            })

            /**
             * Animations
             */
            this.third.animationMixers.add(this.man.animation.mixer)
            object.animations.forEach(animation => {
              if (animation.name) {
                this.man.animation.add(animation.name, animation)
              }
            })
            this.man.animation.play('idle')

            /**
             * Add the player to the scene with a body
             */
            this.third.add.existing(this.man)
            this.third.physics.add.existing(this.man, {
              shape: 'sphere',
              radius: 0.25,
              width: 0.5,
              offset: { y: -0.25 }
            })
            this.man.body.setFriction(0.8)
            this.man.body.setAngularFactor(0, 0, 0)

            // https://docs.panda3d.org/1.10/python/programming/physics/bullet/ccd
            this.man.body.setCcdMotionThreshold(1e-7)
            this.man.body.setCcdSweptSphereRadius(0.25)

            /**
             * Add 3rd Person Controls
             */
            this.controls = new ThirdPersonControls(this.third.camera, this.man, {
              offset: new THREE.Vector3(0, 1, 0),
              targetRadius: 3
            })
            // set initial view to 90 deg theta
            this.controls.theta = 90

            /**
             * Add Pointer Lock and Pointer Drag
             */
            if (!isTouchDevice) {
              let pl = new PointerLock(this.game.canvas)
              let pd = new PointerDrag(this.game.canvas)
              pd.onMove(delta => {
                if (pl.isLocked()) {
                  this.moveTop = -delta.y
                  this.moveRight = delta.x
                }
              })
            }
          })

          /**
           * Add Keys
           */
          this.keys = {
            a: this.input.keyboard.addKey('a'),
            w: this.input.keyboard.addKey('w'),
            d: this.input.keyboard.addKey('d'),
            s: this.input.keyboard.addKey('s'),
            space: this.input.keyboard.addKey(32)
          }

          /**
           * Add joystick
           */
          if (isTouchDevice) {
            const joystick = new JoyStick()
            const axis = joystick.add.axis({
              styles: { left: 35, bottom: 35, size: 100 }
            })
            axis.onMove(event => {
              /**
               * Update Camera
               */
              const { top, right } = event
              this.moveTop = top
              this.moveRight = right
            })
            const buttonA = joystick.add.button({
              letter: 'A',
              styles: { right: 35, bottom: 110, size: 80 }
            })
            buttonA.onClick(() => this.jump())
            const buttonB = joystick.add.button({
              letter: 'B',
              styles: { right: 110, bottom: 35, size: 80 }
            })
            buttonB.onClick(() => (this.move = true))
            buttonB.onRelease(() => (this.move = false))
          }
        }

        jump() {
          if (!this.man || !this.canJump) return
          this.canJump = false
          this.man.animation.play('jump_running', 500, false)
          this.time.addEvent({
            delay: 650,
            callback: () => {
              this.canJump = true
              this.man.animation.play('idle')
            }
          })
          this.man.body.applyForceY(6)
        }

        update(time, delta) {
          if (this.man && this.man.body) {
            /**
             * Update Controls
             */
            this.controls.update(this.moveRight * 2, -this.moveTop * 2)
            if (!isTouchDevice) this.moveRight = this.moveTop = 0
            /**
             * Player Turn
             */
            const speed = 4
            const v3 = new THREE.Vector3()

            const rotation = this.third.camera.getWorldDirection(v3)
            const theta = Math.atan2(rotation.x, rotation.z)
            const rotationMan = this.man.getWorldDirection(v3)
            const thetaMan = Math.atan2(rotationMan.x, rotationMan.z)
            this.man.body.setAngularVelocityY(0)

            const l = Math.abs(theta - thetaMan)
            let rotationSpeed = isTouchDevice ? 2 : 4
            let d = Math.PI / 24

            if (l > d) {
              if (l > Math.PI - d) rotationSpeed *= -1
              if (theta < thetaMan) rotationSpeed *= -1
              this.man.body.setAngularVelocityY(rotationSpeed)
            }

            /**
             * Player Move
             */
            if (this.keys.w.isDown || this.move) {
              if (this.man.animation.current === 'idle' && this.canJump) this.man.animation.play('run')

              const x = Math.sin(theta) * speed,
                y = this.man.body.velocity.y,
                z = Math.cos(theta) * speed

              this.man.body.setVelocity(x, y, z)
            } else {
              if (this.man.animation.current === 'run' && this.canJump) this.man.animation.play('idle')
            }

            /**
             * Player Jump
             */
            if (this.keys.space.isDown && this.canJump) {
              this.jump()
            }
          }
        }
      }

      const config = {
        type: Phaser.WEBGL,
        transparent: true,
        scale: {
          mode: Phaser.Scale.FIT,
          autoCenter: Phaser.Scale.CENTER_BOTH,
          width: window.innerWidth * Math.max(1, window.devicePixelRatio / 2),
          height: window.innerHeight * Math.max(1, window.devicePixelRatio / 2)
        },
        scene: [MainScene],
        ...Canvas({ antialias: false })
      }

      window.addEventListener('load', () => {
        enable3d(() => new Phaser.Game(config)).withPhysics('/lib/ammo/kripken')
      })
    </script>
  </body>
</html>
